import { ISelect } from '../types';
import { Inject, Injectable } from '../decorators/inject';
import { Config } from './config';
import { NExcel } from '../types';
import { createElement } from '../utils/dom-helper';
import { EventHelper } from '../utils/event-helper';
import { TableDatas } from './table-datas';
import { fromEvent } from 'rxjs';
import { filter } from 'rxjs/operators';
import { KeyCode } from '../vars/key-code';
import { TSelectRanage, TSelectBorder } from '../types/common.interface';
import { Hook } from './hook';
import './select.less';
import { TableEvent } from './table-event';
import { RenderTrigger } from './render-trigger';
import { MergeCell } from './merge-cell';
import { Cache } from './cache';
import { HandleError } from './handle-error';
import { AddHook } from '../decorators/add-hook';
import { toJSON, Transform} from '../decorators/toJSON';
import { FreezeRender } from './freeze-render';
@Injectable('Select')
@Transform()
export class Select implements ISelect {
    protected selectDoms: TSelectBorder;
    range: TSelectRanage[] = [];
    @toJSON(null, {name: 'select'})
    protected _selected: TSelectRanage = null;
    // protected _selected: TSelectRanage = {row: -1, col: -1, rowspan: -1, colspan: 5};
    // protected _selected: TSelectRanage = {row: -1, col: 1, rowspan: -1, colspan: 5};
    get selected() {
        return this._selected;
    }
    set selected(val: TSelectRanage) {
        this.fixedSelectBoxPosition(val);
        this.conf.setCurrentSelectedRange(val);
        this._selected = val;
        this.hook.afterSelect.excute(val);
    }
    constructor(
        @Inject(NExcel.OperateContainerToken) protected ele: HTMLElement,
        protected conf: Config,
        protected datas: TableDatas,
        protected hook: Hook,
        protected tableEvent: TableEvent,
        protected renderTrigger: RenderTrigger,
        protected mergeCell: MergeCell,
        protected cache: Cache,
        protected handlerErr: HandleError,
        protected freezeRender: FreezeRender
    ) {
        
    }
    public init() {
        this.initSelectDom();
        this.bindMouseSelectEvent();
        this.bindDotDragEvent();
        this.bindKeyboardEvent();
        this.clearSelected();
        this.hook.afterBgRender.add(() => {
            this.fixedSelectBoxPosition(this.selected);
        });
    }
    public ininEvent() {
        
    }
    public checkSelected() {
        // console.log(this.selected,this.mergeCell.unionRange(this.selected))
        this.selected = this.mergeCell.unionRange(this.selected);
    }
    protected factory(...datas: any[]): any {

    }
    protected initSelectDom() {
        this.selectDoms = {
            left: createElement('div', 'z-table-select-left'),
            right: createElement('div', 'z-table-select-right'),
            top: createElement('div', 'z-table-select-top'),
            bottom: createElement('div', 'z-table-select-bottom'),
            container: createElement('div', 'z-table-select'),
            dot: createElement('div', 'z-table-select-dot'),
            bg: createElement('div', 'z-table-select-bg')
        }
        this.selectDoms.container.appendChild(this.selectDoms.top);
        this.selectDoms.container.appendChild(this.selectDoms.right);
        this.selectDoms.container.appendChild(this.selectDoms.bottom);
        this.selectDoms.container.appendChild(this.selectDoms.left);
        this.selectDoms.container.appendChild(this.selectDoms.dot);
        this.selectDoms.container.appendChild(this.selectDoms.bg);
        this.ele.appendChild(this.selectDoms.container);
        this.selectDoms.container.addEventListener('click', (evt: MouseEvent) =>{
            evt.preventDefault();
            evt.stopPropagation();
            return false;
        });
    }
    protected bindKeyboardEvent() {
        fromEvent<KeyboardEvent>(document, 'keydown')
        .pipe(filter(() => {
            return this.selected != null;
        }))
        .subscribe((evt) => {
            let range = this.selected;
            switch(evt.keyCode) {
                case KeyCode.ArrowUp: 
                    if(range.row == 0) {
                        return;
                    }
                    range.row = range.row - 1;
                    this.selected = range;
                    break;
                case KeyCode.ArrowRight:
                    range.col += 1;
                    this.selected = range;
                    break;
                case KeyCode.ArrowDown:
                    range.row += 1;
                    this.selected = range;
                    break;
                case KeyCode.ArrowLeft:
                    if(range.col == 0) {
                        return;
                    }
                    range.col -= 1;
                    this.selected = range;
                    break;
            }
        });
    }
    protected bindDotDragEvent() {
        this.selectDoms.dot.addEventListener('click', (evt: MouseEvent) => {
            evt.preventDefault();
            evt.stopPropagation();
            return false;
        });
    }
    public clearSelected() {
        this.selectDoms.top.setAttribute('style', `display: none;`);
        this.selectDoms.left.setAttribute('style', `display: none;`);
        this.selectDoms.bottom.setAttribute('style', `display: none;`);
        this.selectDoms.right.setAttribute('style', `display: none;`);
        this.selectDoms.bg.setAttribute('style', `display: none;`);
        this.selectDoms.dot.setAttribute('style', `display: none;`);
    }
    protected isInBody(evt: MouseEvent) {
        if(evt.offsetX < this.conf.header.row.width / this.conf.pixelRatio) {
            return false;
        }
        if(evt.offsetY < this.conf.header.col.height / this.conf.pixelRatio) {
            return false;
        }
        return true;
    }
    protected bindMouseSelectEvent() {
        EventHelper.move(
            this.ele, 
            (evt: MouseEvent) => {
                if(evt.target != this.ele) {
                    return null;
                }
                if(this.isInBody(evt) == false) {
                    return null;
                }
                this.hook.beforeSelect.excute();
                let position = {
                    row: this.cache.getPointRow(evt.offsetY * this.conf.pixelRatio),
                    col: this.cache.getPointCol(evt.offsetX * this.conf.pixelRatio)
                };
                let range: TSelectRanage = {
                    row: position.row,
                    col: position.col,
                    rowspan: 1,
                    colspan: 1
                };
                this.selected = this.mergeCell.unionRange(range);
                this.selectDoms.container.style.pointerEvents = 'none';
                return position;
            }, (position: {row: number, col: number}, evt: MouseEvent) => {
                this.selectDoms.container.style.pointerEvents = 'auto';
                return 2;
            }, 
            (position: {row: number, col: number}, evt: MouseEvent) => {
                try {
                    if(evt.target != this.ele) {
                        return null;
                    }
                    if(position == null) {
                        return ;
                    }
                    let currenPostion = {
                        row: this.cache.getPointRow(evt.offsetY * this.conf.pixelRatio),
                        col: this.cache.getPointCol(evt.offsetX * this.conf.pixelRatio)
                    };
                    let range: TSelectRanage = {
                        row: position.row > currenPostion.row ? currenPostion.row : position.row,
                        col: position.col > currenPostion.col ? currenPostion.col : position.col,
                        rowspan: Math.abs(position.row - currenPostion.row) + 1,
                        colspan: Math.abs(position.col - currenPostion.col) + 1
                    }
                    let newRange = this.mergeCell.unionRange(range);
                    if(newRange) {

                    } else {
                        
                    }
                    this.selected = this.mergeCell.unionRange(range);
                    this.hook.afterSelect.excute(this.selected);
                    return 3;    
                } catch (error) {
                    console.log(error)
                    // console.log(position)
                }
            }
        );
        EventHelper.move(
            this.ele,
            (evt: MouseEvent):any => {
                if(evt.target != this.ele) {
                    return null;
                }
                if(evt.offsetX < this.conf.header.row.width / this.conf.pixelRatio && evt.offsetY > this.conf.header.col.height / this.conf.pixelRatio) {
                    this.hook.beforeSelect.excute();
                    let row = this.cache.getPointRow(evt.offsetY  * this.conf.pixelRatio);
                    this.selectDoms.container.style.pointerEvents = 'none';
                    this.selected = {row: row, col: -1, rowspan: 1, colspan: -1};
                    return {row: row, col: -1};
                }
                if(evt.offsetY < this.conf.header.col.height / this.conf.pixelRatio && evt.offsetX > this.conf.header.row.width / this.conf.pixelRatio) {
                    this.hook.beforeSelect.excute();
                    let col = this.cache.getPointCol(evt.offsetX * this.conf.pixelRatio);
                    this.selectDoms.container.style.pointerEvents = 'none';
                    this.selected = {row: -1, col: col, rowspan: -1, colspan: 1};
                    return {row: -1, col: col};
                }
                if(evt.offsetY < this.conf.header.col.height / this.conf.pixelRatio && evt.offsetX < this.conf.header.row.width / this.conf.pixelRatio) {
                    this.selected = {row: -1, col: -1, rowspan: -1, colspan: -1};
                    return {row: -1, col: -1};
                }
                return null;
            },
            () => {
                this.selectDoms.container.style.pointerEvents = 'auto';
            },
            (position: {row: number, col: number},  evt: MouseEvent) => {
                if(evt.target != this.ele || position === null) {
                    return ;
                }
                if(position.row !== -1 && position.col ===  -1) {
                    let row = this.cache.getPointRow(evt.offsetY  * this.conf.pixelRatio);
                    let range: TSelectRanage = {
                        row: position.row > row ? row : position.row,
                        col: -1,
                        rowspan: Math.abs(position.row - row) + 1,
                        colspan: -1
                    }
                    this.selected = range;
                    this.hook.afterSelect.excute(range);
                }
                if(position.row === -1 && position.col !== -1) {
                    let col = this.cache.getPointCol(evt.offsetX * this.conf.pixelRatio);
                    let range: TSelectRanage = {
                        row: -1,
                        col: position.col > col ? col : position.col,
                        rowspan: -1,
                        colspan: Math.abs(position.col - col) + 1
                    }
                    this.selected = range;
                    this.hook.afterSelect.excute(range);
                }
                if(position.row === -1 && position.col === -1) {
                    this.selected = {row: -1, col: -1, rowspan: -1, colspan: -1};
                    this.hook.afterSelect.excute(this.selected);
                }
            }
        );
    }
    
    protected fixRenderSelectDomBoundary(range: TSelectRanage): TSelectRanage {
        if(range == null) {
            return range;
        }
        let resultRange = {...range};
        let { row, col, rowspan, colspan} = range;

        const freezeRow = this.conf.freeze.row;
        let miniRow = this.conf.visibleBoundary.row;
        let maxRow = this.conf.visibleBoundary.endRow;
        if(freezeRow !== -1) {
            if(freezeRow + 1 !== this.conf.visibleBoundary.row) {
                if(row <= freezeRow) {
                    miniRow = 0;
                    maxRow = freezeRow;
                }
            } else {
                miniRow = 0;
            }
        }

        if(row < miniRow) {
            if(row + rowspan - 1 < miniRow) {
                return null;
            }
            resultRange.row = miniRow;
            resultRange.rowspan = rowspan - (miniRow - row);
        }

        if(row + rowspan - 1 > maxRow) {
            resultRange.rowspan = maxRow - resultRange.row + 1;
        }

        if(resultRange.row + resultRange.rowspan - 1 < miniRow) {
            return null;
        }

        if(resultRange.row > maxRow) {
            return null;
        }

        const freezeCol = this.conf.freeze.col;
        let minCol = this.conf.visibleBoundary.col;
        let maxCol = this.conf.visibleBoundary.endCol;
        if(freezeCol !== -1) {
            if(freezeCol + 1 !== this.conf.visibleBoundary.col) {
                if(col <= freezeCol) {
                    minCol = 0;
                    maxCol = freezeCol;
                }
            } else {
                minCol = 0;
            }
        }
        if(col < minCol) {
            if(col + colspan - 1 < minCol) {
                return null;
            }
            resultRange.col = minCol;
            resultRange.colspan = colspan - (minCol - col);
        }
        if(col + colspan - 1 > maxCol) {
            resultRange.colspan = maxCol - resultRange.col + 1;
        }
        if(resultRange.col + resultRange.colspan - 1 < minCol) {
            return null;
        }
        if(resultRange.col > maxCol) {
            return null;
        }
        return resultRange;
    }
    protected renderSelectFull(range: TSelectRanage): null | undefined | void {
        let startPoint = {x: 0, y: 0};
        if(range.row !== -1 && range.col === -1) { // 选中整行
            if(range.row + range.rowspan - 1 < this.conf.visibleBoundary.row || range.row > this.conf.visibleBoundary.endRow) {
                this.clearSelected();
                return null;
            }
            let startRow = range.row < this.conf.visibleBoundary.row ? this.conf.visibleBoundary.row : range.row;
            let endRow = range.row + range.rowspan > this.conf.visibleBoundary.endRow ? this.conf.visibleBoundary.endRow : range.row + range.rowspan;
            let info = this.cache.getRowRangePositionInfo(startRow, endRow - 1);
            startPoint.y = info.start / this.conf.pixelRatio;
            let endPoint = {x: startPoint.x, y: startPoint.y};
            endPoint.y = info.end / this.conf.pixelRatio;
            this.selectDoms.top.setAttribute('style', `
                top: ${startPoint.y}px;
                left: ${this.conf.header.row.width / this.conf.pixelRatio - 1}px;
                right: 0px;
                width: ${this.ele.clientWidth - (this.conf.header.row.width / this.conf.pixelRatio)}px;
            `);
            
            this.selectDoms.left.setAttribute('style', `
                top: ${startPoint.y}px;
                left: ${this.conf.header.row.width / this.conf.pixelRatio}px;
                height: ${endPoint.y - startPoint.y}px;
            `);

            this.selectDoms.bottom.setAttribute('style', `
                top: ${endPoint.y}px;
                left: ${this.conf.header.row.width / this.conf.pixelRatio - 1}px;
                right: 0px;
                width: ${this.ele.clientWidth - (this.conf.header.row.width / this.conf.pixelRatio)}px;
            `);
            this.selectDoms.right.setAttribute('style', `display: none;`)
            this.selectDoms.bg.setAttribute('style', `
                top: ${startPoint.y}px;
                left: ${this.conf.header.row.width / this.conf.pixelRatio}px;
                right: 0px;
                width: ${this.ele.clientWidth - (this.conf.header.row.width / this.conf.pixelRatio)}px;
                height: ${endPoint.y - startPoint.y + 1}px;
            `);
        }
        if(range.row === -1 && range.col !== -1) { // 选中整列
            if(range.col + range.colspan - 1 < this.conf.visibleBoundary.col || range.col > this.conf.visibleBoundary.endCol) {
                return this.clearSelected();
            }
            let startCol = range.col < this.conf.visibleBoundary.col ? this.conf.visibleBoundary.col : range.col;
            let endCol = range.col + range.colspan > this.conf.visibleBoundary.endCol ? this.conf.visibleBoundary.endCol : range.col + range.colspan;
            let info = this.cache.getColRangePositionInfo(startCol, endCol - 1);
            let startX = info.start / this.conf.pixelRatio;
            let endX = info.end / this.conf.pixelRatio;
            this.selectDoms.bottom.setAttribute('style', 'display: none;');
            this.selectDoms.top.setAttribute('style', `
                top: ${this.conf.header.col.height / this.conf.pixelRatio - 1}px;
                left: ${startX}px;
                width: ${endX - startX}px;
            `);

            this.selectDoms.left.setAttribute('style', `
                top: ${this.conf.header.col.height / this.conf.pixelRatio - 1}px;
                left: ${startX}px;
                height: ${this.ele.clientHeight - this.conf.header.col.height / this.conf.pixelRatio + 1}px;
            `);

            this.selectDoms.right.setAttribute('style', `
                top: ${startPoint.y + this.conf.header.col.height / this.conf.pixelRatio - 1}px;
                left: ${endX}px;
                height: ${this.ele.clientHeight - this.conf.header.col.height / this.conf.pixelRatio + 1}px;
            `);

            this.selectDoms.bg.setAttribute('style', `
                top: ${this.conf.header.col.height / this.conf.pixelRatio - 1}px;
                left: ${startX}px;
                width: ${endX - startX}px;
                height: ${this.ele.clientHeight - this.conf.header.col.height / this.conf.pixelRatio + 1}px;
            `);
        }
        if(range.row === -1 && range.col === -1) { // 全选
            this.selectDoms.top.setAttribute('style', `
                top: ${this.conf.header.col.height / this.conf.pixelRatio - 1}px;
                left: ${startPoint.x + this.conf.header.row.width / this.conf.pixelRatio - 1}px;
                width: ${this.ele.clientWidth - (startPoint.x + this.conf.header.row.width / this.conf.pixelRatio - 1)}px;
            `);
            this.selectDoms.left.setAttribute('style', `
                top: ${this.conf.header.col.height / this.conf.pixelRatio - 1}px;
                left: ${startPoint.x + this.conf.header.row.width / this.conf.pixelRatio - 1}px;
                height: ${this.ele.clientHeight - this.conf.header.col.height / this.conf.pixelRatio + 1}px;
            `);
            this.selectDoms.right.setAttribute('style', 'display: none;');
            this.selectDoms.bottom.setAttribute('style', 'display:none;')
            this.selectDoms.bg.setAttribute('style', `
                top: ${this.conf.header.col.height / this.conf.pixelRatio - 1}px;
                left: ${startPoint.x + this.conf.header.row.width / this.conf.pixelRatio - 1}px;
                width: ${this.ele.clientWidth - (startPoint.x + this.conf.header.row.width / this.conf.pixelRatio - 1)}px;
                height: ${this.ele.clientHeight - this.conf.header.col.height / this.conf.pixelRatio + 1}px;
            `);
        }
        this.selectDoms.dot.setAttribute('style', `display: none;`);
    }
    protected fixedSelectBoxPosition(range: TSelectRanage) {
        if(range ===  null) {
            return this.clearSelected();
        };
        if((range.row === -1 || range.col === -1)) {
            return this.renderSelectFull(range);
        }
        range = this.fixRenderSelectDomBoundary(range);
        if(range == null) {
            return this.clearSelected();
        }
        let start = {row: range.row, col: range.col};
        let end = { row: range.rowspan + range.row - 1, col: range.colspan + range.col - 1};
        // console.log(end)
        let _info = this.cache.getRangeRectInfo(start, end);
        if(start.row > this.conf.freeze.row) {
            const startY = this.freezeRender.getStartRowHeight() + this.conf.header.col.height;
            if(_info.start.y < startY) {
                _info.start.y = startY
            }
        }
        if(start.col > this.conf.freeze.col) {
            const startX = this.freezeRender.getStartColWidth() + this.conf.header.row.width;
            if(_info.start.x < startX) {
                _info.start.x = startX;
            }
        }
        let info = {
            start: {
                x: _info.start.x / this.conf.pixelRatio,
                y: _info.start.y / this.conf.pixelRatio
            },
            end: {
                x: _info.end.x / this.conf.pixelRatio,
                y: _info.end.y / this.conf.pixelRatio
            }
        }
        this.selectDoms.top.setAttribute('style', `
            top: ${info.start.y}px;
            left: ${info.start.x}px;
            width: ${info.end.x - info.start.x}px;
        `);
        this.selectDoms.left.setAttribute('style', `
            top: ${info.start.y}px;
            left: ${info.start.x}px;
            height: ${info.end.y - info.start.y + 1}px;
        `);
        this.selectDoms.bottom.setAttribute('style', `
            top: ${info.end.y}px;
            left: ${info.start.x}px;
            width: ${info.end.x - info.start.x}px;
        `);
        this.selectDoms.right.setAttribute('style', `
            top: ${info.start.y}px;
            left: ${info.end.x - 1}px;
            height: ${info.end.y - info.start.y + 1}px;
        `);
        this.selectDoms.bg.setAttribute('style', `
            top: ${info.start.y}px;
            left: ${info.start.x}px;
            width: ${info.end.x - info.start.x}px;
            height: ${info.end.y - info.start.y}px;
        `);
        this.selectDoms.dot.setAttribute('style', `
            left: ${info.end.x - 2}px;
            top: ${info.end.y - 2}px;
        `);
    }
    @AddHook('afterDeleteRow')
    protected selectEffectByDeleteRow() {
        this.selected = null;
    }
    @AddHook('afterDeleteCol')
    protected selectEffectByDeleteCol() {
        this.selected = null;
    }
}
